﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace OmniKits.Core
{
    using NullGuard;

    using OmniKits.Extensions;

    public class LazyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
#pragma warning disable 1591

        private ConcurrentDictionary<TKey, Lazy<TValue>> _LazyDictionary;
        private LazyThreadSafetyMode _Mode;

        #region constructors

        public LazyDictionary(LazyThreadSafetyMode mode = LazyThreadSafetyMode.ExecutionAndPublication)
            : this(new ConcurrentDictionary<TKey, Lazy<TValue>>(), mode)
        { }

        public LazyDictionary(IEqualityComparer<TKey> comparer, LazyThreadSafetyMode mode = LazyThreadSafetyMode.ExecutionAndPublication)
            : this(new ConcurrentDictionary<TKey, Lazy<TValue>>(comparer), mode)
        { }

        public LazyDictionary(int concurrencyLevel, int capacity, LazyThreadSafetyMode mode = LazyThreadSafetyMode.ExecutionAndPublication)
            : this(new ConcurrentDictionary<TKey, Lazy<TValue>>(concurrencyLevel, capacity), mode)
        { }

        public LazyDictionary(int concurrencyLevel, int capacity, IEqualityComparer<TKey> comparer, LazyThreadSafetyMode mode = LazyThreadSafetyMode.ExecutionAndPublication)
            : this(new ConcurrentDictionary<TKey, Lazy<TValue>>(concurrencyLevel, capacity, comparer), mode)
        { }

        private LazyDictionary(ConcurrentDictionary<TKey, Lazy<TValue>> lazyDictionary, LazyThreadSafetyMode mode)
        {
            _LazyDictionary = lazyDictionary;
            _Mode = mode;
        }

        #endregion

        public static Lazy<TValue> MakeLazy([AllowNull]TValue value) => new Lazy<TValue>(() => value, false);

        #region IDictionary<TKey, TValue>

        public TValue this[TKey key]
        {
            get
            {
                return _LazyDictionary[key].Value;
            }
            set
            {
                _LazyDictionary[key] = MakeLazy(value);
            }
        }

        public int Count => _LazyDictionary.Count;

        public bool IsReadOnly { get; } = false;

        public ICollection<TKey> Keys => _LazyDictionary.Keys;

        public ICollection<TValue> Values => _LazyDictionary.Values.Select(lazy => lazy.Value).ToArray();

        public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);

        public void Add(TKey key, [AllowNull]TValue value) => _LazyDictionary.As<IDictionary<TKey, Lazy<TValue>>>().Add(key, MakeLazy(value));

        public void Clear() => _LazyDictionary.Clear();

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            TValue value;
            if (TryGetValue(item.Key, out value))
                if (value.Is(item.Value))
                    return true;

            return false;
        }

        public bool ContainsKey(TKey key) => _LazyDictionary.ContainsKey(key);

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            if (arrayIndex < 0 || arrayIndex >= array.Length)
                throw new ArgumentException(null, nameof(arrayIndex));

            foreach (var item in _LazyDictionary)
                array[arrayIndex++] = new KeyValuePair<TKey, TValue>(item.Key, item.Value.Value);
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            foreach (var item in _LazyDictionary)
                yield return new KeyValuePair<TKey, TValue>(item.Key, item.Value.Value);
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            Lazy<TValue> lazy;
            if (_LazyDictionary.TryGetValue(item.Key, out lazy))
                if (lazy.Value.Is(item.Value))
                    return _LazyDictionary.As<IDictionary<TKey, Lazy<TValue>>>().Remove(new KeyValuePair<TKey, Lazy<TValue>>(item.Key, lazy));

            return false;
        }

        public bool Remove(TKey key) => _LazyDictionary.As<IDictionary<TKey, Lazy<TValue>>>().Remove(key);

        public bool TryGetValue(TKey key, out TValue value)
        {
            Lazy<TValue> lazy;
            if (_LazyDictionary.TryGetValue(key, out lazy))
            {
                value = lazy.Value;
                return true;
            }
            value = default(TValue);
            return false;
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        #endregion

        #region LazyDictionary<TKey, TValue>

        public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) => GetOrAdd(key, () => valueFactory(key));
        public TValue GetOrAdd(TKey key, Func<TValue> valueFactory) => _LazyDictionary.GetOrAdd(key, new Lazy<TValue>(valueFactory, _Mode)).Value;
        public TValue GetOrAdd(TKey key, [AllowNull]TValue value) => _LazyDictionary.GetOrAdd(key, MakeLazy(value)).Value;

        #endregion
    }
}
